﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using CoreEx.Common.Linq;
using CoreEx.Database.Metadata;
using LinFu.IoC.Configuration;

namespace CoreEx.Database.Linq.Implementation
{
    [Implements(typeof(ILoadOptionFactory))]
    public class LoadOptionFactory : ILoadOptionFactory
    {
        IDictionary<Type, IList<LoadOption>> _loadOptions = new Dictionary<Type, IList<LoadOption>>();

        private ITypeInfoMapper _typeInfoMapper;

        public IDictionary<Type, IList<LoadOption>> CreateLoadOptions(Expression expression, ITypeInfoMapper typeInfoMapper)
        {
            _typeInfoMapper = typeInfoMapper;
            
            var constantExpressions = expression.Find<ConstantExpression>(c => typeof (ILoadOptions).IsAssignableFrom(c.Value.GetType()));
            foreach (var constantExpression in constantExpressions)
            {
                CreateLoadOptions((ILoadOptions) constantExpression.Value);
            }
            return _loadOptions;
        }

        private void CreateLoadOptions(ILoadOptions loadOptions)
        {
            foreach (var loadExpression in loadOptions.Expressions)
            {
                var lambdaExpression = (LambdaExpression) loadExpression.StripQuotes();
                Type targetType = GetTargetType(lambdaExpression);
                LoadOption loadOption = new LoadOption();
                loadOption.LoadExpression = lambdaExpression;
                loadOption.PropertyMapping = _typeInfoMapper.MapFrom(targetType).ColumnMappings[GetTargetPropertyFromLoadExpression(lambdaExpression.Body)];
                if (!_loadOptions.ContainsKey(targetType))
                    _loadOptions.Add(targetType, new List<LoadOption>());
                _loadOptions[targetType].Add(loadOption);
            }
        }

        private void ValidateLoadExpression(Expression expression)
        {
            var memberExpression = expression as MemberExpression;
            if (memberExpression != null)
                ValidateMemberExpression((PropertyInfo)memberExpression.Member);
            
        }


        private PropertyInfo GetTargetPropertyFromLoadExpression(Expression expression)
        {
            MemberExpression memberExpression = null;

            if (expression is MemberExpression)
                memberExpression = (MemberExpression)expression;

            if (expression is MethodCallExpression)
            {
                var methodCallExpression = (MethodCallExpression)expression;
                memberExpression = (MemberExpression)methodCallExpression.Arguments[0];
            }
            if (memberExpression != null)
                return (PropertyInfo)memberExpression.Member;
            return null;
        }

        private void ValidateMemberExpression(PropertyInfo propertyInfo)
        {
            if (!propertyInfo.IsNaviationProperty())
                throw new ArgumentException("The property must be a navigation property", "propertyInfo");            
        }


        private static Type GetTargetType(LambdaExpression lambdaExpression)
        {
            var funcDelegateType = lambdaExpression.GetType().GetGenericArguments().FirstOrDefault();
            var loadExpressionTargetType = funcDelegateType.GetGenericArguments().FirstOrDefault();
            return loadExpressionTargetType;
        }

    }
}
